package orm

import (
	"fmt"
	"gocamp2022/homework2/internal/errs"
	"gocamp2022/homework2/model"
	"reflect"
)

type Updater[T any] struct {
	builder
	db      *DB
	assigns []Assignable
	val     *T
	where   []Predicate
}

func NewUpdater[T any](db *DB) *Updater[T] {
	return &Updater[T]{
		db: db,
		builder: builder{
			dialect: db.dialect,
			quoter:  db.dialect.quoter(),
		},
	}
}

func (u *Updater[T]) Update(t *T) *Updater[T] {
	u.val = t
	return u
}

func (u *Updater[T]) Set(assigns ...Assignable) *Updater[T] {
	u.assigns = assigns
	return u
}

func (u *Updater[T]) Build() (*Query, error) {

	if u.assigns == nil {
		return nil, errs.ErrNoUpdatedColumns
	}

	meta, err := u.db.r.Get(u.val)

	if err != nil {
		return nil, err
	}

	u.model = meta

	val := u.db.valCreator(u.val, meta)

	u.sb.WriteString("UPDATE ")

	u.quote(meta.TableName)

	u.sb.WriteString(" SET ")

	for i, assign := range u.assigns {
		if i > 0 {
			u.sb.WriteString(", ")
		}

		switch assign := assign.(type) {
		case Column:
			fd, ok := meta.FieldMap[assign.name]
			if !ok {
				return nil, errs.NewErrUnknownField(assign.name)
			}

			// value
			v, err := val.Field(assign.name)

			if err != nil {
				return nil, err
			}

			u.quote(fd.ColName)
			u.sb.WriteString("=")

			_ = u.buildExpression(value{val: v})

		case RawExpr:
			u.sb.WriteString(assign.raw)
		case Assignment:

			fd, ok := meta.FieldMap[assign.column]

			if !ok {
				fd, ok = meta.ColumnMap[assign.column]
			}

			if !ok {
				return nil, errs.NewErrUnknownField(assign.column)
			}

			u.quote(fd.ColName)

			u.sb.WriteString("=")

			err = u.buildExpression(assign.val)

			if err != nil {
				return nil, err
			}
		}
	}

	if len(u.where) > 0 {
		u.sb.WriteString(" WHERE ")
		if err := u.buildPredicates(u.where); err != nil {
			return nil, err
		}
	}

	u.sb.WriteString(";")

	return &Query{
		SQL:  u.sb.String(),
		Args: u.args,
	}, nil
}

func (u *Updater[T]) Where(predicates ...Predicate) *Updater[T] {
	u.where = predicates
	return u
}

// AssignNotZeroColumns 更新非零值
func AssignNotZeroColumns(entity interface{}) []Assignable {
	var result []Assignable

	// 读取结构体的元数据，排除零值
	reg := model.NewRegistry()
	meta, err := reg.Get(entity)

	if err != nil {
		return nil
	}

	elem := reflect.ValueOf(entity).Elem()

	for _, fd := range meta.Fields {

		if fd == nil {
			continue
		}

		v := elem.FieldByName(fd.GoName)

		if v.IsZero() {
			continue
		}

		result = append(result, Assign(fd.ColName, v.Interface()))
	}

	fmt.Printf("%#v", result)
	return result
}
